Topic: 開放資料 - 黃金價格深度學習預測應用(LSTM)¶

File: tutorial_gold_prediction_lstm.ipynb

Author: Ming-Chang Lee

Date: 2024.12.08

References:

https://github.com/rwepa/python_data_scientist

大綱¶

  1. CRISP-DM 六大步驟
  2. 黃金價格深度學習預測應用(LSTM)
  3. 台灣股市,ETF下載

1. CRISP-DM 六大步驟¶

  1. 步驟 1:商業理解
  2. 步驟 2:資料理解
  3. 步驟 3:資料準備
  4. 步驟 4:建立模型
  5. 步驟 5:模型評估與測試
  6. 步驟 6:佈署應用

2. 黃金價格深度學習預測應用(LSTM)¶

步驟 1:商業理解¶

目標是使用 Yahoo 開放式財金資料, 建立深度學習預測應用(LSTM).

黃金常用投資方法

  1. 實體黃金(不建議)

  2. 黃金存摺

  3. 黃金ETF(指數股票型基金, Exchange-Traded Funds, ETF)-5種商品

    (1). 黃金價格追蹤ETF: ETF主要目的是追蹤黃金價格變化, 該ETF通常會真正的持有實物黃金(如金塊或金條)來追蹤黃金價格.

    (2). 黃金礦業ETF: 主要持有金礦公司的股票, 投資人投資的項目不是「實物黃金」, 而是購買挖礦業者的股票, 等於是通過黃金礦業的興衰來參與黃金市場.

    (3). 國際和地區性ETF: 例: 購買美國、加拿大、澳大利亞或南非等地的黃金ETF, 是以「地區」、「國家」作為畫分區隔.

ETF範例:

  • 00635U(台股): 元大S&P黃金
  • 00708L(台股): 元大S&P黃金正2
  • GLD: SPDR黃金指數型基金, 全球最大的黃金指數型基金
  • IAU: iShares黃金信託ETF
  • SLV: iShares白銀信託ETF
  • GC: 紐約商業交易所的黃金期貨ETF (New York Stock Exchange, NYMEX)
  • MGC: 芝加哥期貨交易所的黃金期貨ETF (Chicago Board of Trade, CBOT)

黃金ETF期貨:

  • 使用黃金期貨來達成投資黃金的策略, 該ETF不會真正的持有實物黃金, 是以國際黃金市場未來某時點的黃金價格為交易標的的期貨合約, 而這類ETF由於具有時間價值的意義並具有較高的波動性.
  • 本研究資料集為黃金期貨代號為 GC.

黃金期貨注意事項:

  • 優點:進入門檻低, 且不需要保管成本.
  • 缺點:期貨合約會到期, 需要不停更換新合約, 無法放著不理.
  1. 黃金CFD(差價合約, Contract For Difference, CFD)

  2. 黃金相關個股

黃金單位:

  • 1公克 = 0.26667台錢 = 0.02667台兩
  • 1盎司(ounce, 簡寫為 oz, 英兩) = 31.1035公克 = 8.29437台錢 = 0.829437台兩
  • 1盎司大約等於1台兩
  • 雖然黃金的英文是gold,但其在國際金融市場的簡稱卻是XAU,其中X代表它不是任何一國家發行的貨幣,AU則是黃金的化學符號。

查詢財金價格

  • https://tw.stock.yahoo.com/
  • 左上角輸入 GC=F

步驟 2:資料理解¶

In [1]:
# yfinance 模組
# https://pypi.org/project/yfinance/

# 安裝模組:
# pip install yfinance

# 載入模組

# Part 1. 擷取財務金融資料
# yfinance 會自動載入 pandas 模組

import yfinance as yf # yf.download 擷取財金資料

# Part 2. 資料處理
import numpy as np
# import pandas as pd

# Part 3. 資料視覺化
import plotly.express as px
import matplotlib.pyplot as plt

# Part 4. 深度學習
from sklearn.preprocessing import MinMaxScaler
from keras import Model
from keras.layers import Input, Dense, Dropout
from keras.layers import LSTM
from sklearn.metrics import mean_absolute_percentage_error

# 使用 Spyder 設定 plotly 繪圖結果顯示於瀏覽器
# import plotly.io as pio
# pio.renderers.default='browser'
# 使用 Colab, Jupyter-notebook 不用設定此選項

# matplotlib 中文字型 - 設定 matplotlib.rcParams 方法
# https://github.com/rwepa/ipas_bda/blob/main/ipas-python-program.py#L1488

from matplotlib import rcParams
rcParams['font.sans-serif'] = ['Microsoft YaHei'] # 設定中文字型
rcParams['axes.unicode_minus'] = False # 設定負正確顯示
In [2]:
# 匯入資料

# 原參考資料使用 CSV檔案, 本例改為使用即時網路讀取黃金價格資料.
# 資料為紐約商業交易所黃金期貨ETF, 簡稱GC
# end參數為設定結束日期, 結果不包括參數日期, 本例結束日期不包括2023-12-15.

df = yf.download(tickers="GC=F", start="2013-01-01", end="2024-11-30")

# 理解財金指標使用的幣別
gold = yf.Ticker("GC=F") # Gold Futures on Yahoo Finance
gold.history_metadata # dict
print(gold.history_metadata['currency'])  # USD
[*********************100%***********************]  1 of 1 completed
USD

注意: yfinance回傳結果為 MultiIndex。
In [3]:
# 資料檢視
# 2013-01-02 ~ 2023-11-29, 2997列*6行
df
Out[3]:
Price Adj Close Close High Low Open Volume
Ticker GC=F GC=F GC=F GC=F GC=F GC=F
Date
2013-01-02 1687.900024 1687.900024 1693.800049 1670.000000 1672.800049 35
2013-01-03 1673.699951 1673.699951 1686.800049 1662.000000 1686.099976 140
2013-01-04 1648.099976 1648.099976 1658.300049 1625.699951 1647.000000 199
2013-01-07 1645.500000 1645.500000 1659.900024 1643.800049 1656.500000 49
2013-01-08 1661.500000 1661.500000 1661.500000 1647.699951 1647.699951 17
... ... ... ... ... ... ...
2024-11-25 2616.800049 2616.800049 2689.399902 2616.800049 2689.399902 94
2024-11-26 2620.300049 2620.300049 2625.600098 2620.300049 2625.600098 177858
2024-11-27 2639.899902 2639.899902 2657.899902 2627.199951 2633.500000 61653
2024-11-28 2639.699951 2639.699951 2648.600098 2620.699951 2636.399902 61653
2024-11-29 2657.000000 2657.000000 2664.300049 2620.699951 2636.399902 3861

2997 rows × 6 columns

問題: 結果中顯示 ... 的資料值如何顯示?

分析:

  • 方法1: 在 Spyder 右上角 [Variable Explorer] 中按滑鼠左鍵二下並顯示結果.
  • 方法2: 參考RWEPA: https://github.com/rwepa/ipas_bda/blob/main/ipas-python-program.py#L625

pd.set_option('display.expand_frame_repr', False)

pd.set_option('display.max_columns', None)

pd.set_option('display.max_rows', None)

In [4]:
# 資料物件
type(df)
Out[4]:
pandas.core.frame.DataFrame
In [5]:
# 行資料型態
# Adj Close    float64 調整後收盤價
# Close        float64 收盤價
# High         float64 最高價
# Low          float64 最低價
# Open         float64 開盤價
# Volume         int64 總量
df.dtypes
Out[5]:
Price      Ticker
Adj Close  GC=F      float64
Close      GC=F      float64
High       GC=F      float64
Low        GC=F      float64
Open       GC=F      float64
Volume     GC=F        int64
dtype: object
In [6]:
# 索引值
df.index
Out[6]:
DatetimeIndex(['2013-01-02', '2013-01-03', '2013-01-04', '2013-01-07',
               '2013-01-08', '2013-01-09', '2013-01-10', '2013-01-11',
               '2013-01-14', '2013-01-15',
               ...
               '2024-11-18', '2024-11-19', '2024-11-20', '2024-11-21',
               '2024-11-22', '2024-11-25', '2024-11-26', '2024-11-27',
               '2024-11-28', '2024-11-29'],
              dtype='datetime64[ns]', name='Date', length=2997, freq=None)
In [7]:
# 欄位名稱
# MultiIndex
df.columns
Out[7]:
MultiIndex([('Adj Close', 'GC=F'),
            (    'Close', 'GC=F'),
            (     'High', 'GC=F'),
            (      'Low', 'GC=F'),
            (     'Open', 'GC=F'),
            (   'Volume', 'GC=F')],
           names=['Price', 'Ticker'])
In [8]:
# 資料值
df.values
Out[8]:
array([[1.68790002e+03, 1.68790002e+03, 1.69380005e+03, 1.67000000e+03,
        1.67280005e+03, 3.50000000e+01],
       [1.67369995e+03, 1.67369995e+03, 1.68680005e+03, 1.66200000e+03,
        1.68609998e+03, 1.40000000e+02],
       [1.64809998e+03, 1.64809998e+03, 1.65830005e+03, 1.62569995e+03,
        1.64700000e+03, 1.99000000e+02],
       ...,
       [2.63989990e+03, 2.63989990e+03, 2.65789990e+03, 2.62719995e+03,
        2.63350000e+03, 6.16530000e+04],
       [2.63969995e+03, 2.63969995e+03, 2.64860010e+03, 2.62069995e+03,
        2.63639990e+03, 6.16530000e+04],
       [2.65700000e+03, 2.65700000e+03, 2.66430005e+03, 2.62069995e+03,
        2.63639990e+03, 3.86100000e+03]])
In [9]:
# 欄位資料訊息
df.info()
<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 2997 entries, 2013-01-02 to 2024-11-29
Data columns (total 6 columns):
 #   Column             Non-Null Count  Dtype  
---  ------             --------------  -----  
 0   (Adj Close, GC=F)  2997 non-null   float64
 1   (Close, GC=F)      2997 non-null   float64
 2   (High, GC=F)       2997 non-null   float64
 3   (Low, GC=F)        2997 non-null   float64
 4   (Open, GC=F)       2997 non-null   float64
 5   (Volume, GC=F)     2997 non-null   int64  
dtypes: float64(5), int64(1)
memory usage: 163.9 KB
In [10]:
# 資料摘要
# count 個數
# mean  平均值
# std   標準差
# min   最小值
# 25%   25百分位數(Q1)
# 50%   50百分位數(Q2), 中位數 (median)
# 75%   75百分位數(Q3)
# max   最大值
df.describe()
Out[10]:
Price Adj Close Close High Low Open Volume
Ticker GC=F GC=F GC=F GC=F GC=F GC=F
count 2997.000000 2997.000000 2997.000000 2997.000000 2997.000000 2997.000000
mean 1551.721620 1551.721620 1559.282650 1544.210411 1551.794494 5217.011678
std 366.661100 366.661100 368.391433 364.834405 366.485507 28504.857640
min 1050.800049 1050.800049 1062.000000 1046.199951 1053.699951 0.000000
25% 1257.300049 1257.300049 1264.099976 1252.400024 1257.900024 46.000000
50% 1373.099976 1373.099976 1386.800049 1362.900024 1375.000000 163.000000
75% 1823.800049 1823.800049 1829.900024 1814.500000 1821.800049 522.000000
max 2788.500000 2788.500000 2789.000000 2774.600098 2787.399902 386334.000000
In [11]:
# 將 index 轉換為 Date 資料行
df['Date'] = df.index
In [12]:
# 將 Date 移至第1欄位置
df = df[['Date', 'Open', 'High', 'Low', 'Close', 'Adj Close', 'Volume']]
In [13]:
# 刪除 index 值
df = df.reset_index(drop=True)
df
Out[13]:
Price Date Open High Low Close Adj Close Volume
Ticker GC=F GC=F GC=F GC=F GC=F GC=F
0 2013-01-02 1672.800049 1693.800049 1670.000000 1687.900024 1687.900024 35
1 2013-01-03 1686.099976 1686.800049 1662.000000 1673.699951 1673.699951 140
2 2013-01-04 1647.000000 1658.300049 1625.699951 1648.099976 1648.099976 199
3 2013-01-07 1656.500000 1659.900024 1643.800049 1645.500000 1645.500000 49
4 2013-01-08 1647.699951 1661.500000 1647.699951 1661.500000 1661.500000 17
... ... ... ... ... ... ... ...
2992 2024-11-25 2689.399902 2689.399902 2616.800049 2616.800049 2616.800049 94
2993 2024-11-26 2625.600098 2625.600098 2620.300049 2620.300049 2620.300049 177858
2994 2024-11-27 2633.500000 2657.899902 2627.199951 2639.899902 2639.899902 61653
2995 2024-11-28 2636.399902 2648.600098 2620.699951 2639.699951 2639.699951 61653
2996 2024-11-29 2636.399902 2664.300049 2620.699951 2657.000000 2657.000000 3861

2997 rows × 7 columns

In [14]:
# 檢查重複值
df.duplicated().sum()
Out[14]:
0
In [15]:
# 檢查遺漏值, 各變數沒有遺漏值
df.isnull().sum().sum()
Out[15]:
0
In [16]:
# 匯出 Excel 檔案
df.to_excel("gold_price_2013_2024.xlsx")
In [17]:
# 圖1. GC價格走勢圖(2013-2024年)

fig = px.line(x=df[('Date', '')], y=df[('Close', 'GC=F')])
fig.update_traces(line_color='black')
fig.update_layout(xaxis_title="Date", 
                  yaxis_title="Close Price",
                  title={'text': "圖1. GC價格走勢圖(2013-2024年)", 'x':0.5, 'y':0.95,  'xanchor':'center', 'yanchor':'top'},
                  plot_bgcolor='rgba(255, 215, 0, 0.5)')
fig.show()
In [18]:
# 圖2. 黃金價格盒鬚圖

# 考量僅進行各種價格的盒鬚圖, 因此使用 drop 刪除沒有使用的日期, 成交量
# melt: 融化, 將寬資料轉換為長資料, 使用 melt 目的為依照不同價格別繪製群組盒鬚圖

df_price = df[[('Open', 'GC=F'), ('High', 'GC=F'), ('Low', 'GC=F'), ('Close', 'GC=F')]].melt(var_name="price_type")
fig = px.box(df_price, 
             y="value", 
             facet_col="price_type", 
             color="price_type",
             boxmode="overlay")
fig.update_layout(title={'text': "圖2. GC價格盒鬚圖(2013-2024年)", 'x':0.5, 'y':0.95,  'xanchor':'center', 'yanchor':'top'},
                  plot_bgcolor='rgba(255, 215, 0, 0.5)')
fig.show()

步驟 3:資料準備¶

In [19]:
# 將資料區分為訓練集與測試集
# 本例為時間序列資料, 資料不可以隨機抽取, 須保持原資料順序.
# 訓練集 train data: 2013-2022年
# 測試集  test data: 2023~2024年

test_size = df[df.Date.dt.year >=2023].shape[0]
test_size # 482 (16%)
Out[19]:
482
In [20]:
train_size = df.shape[0] - test_size
train_size # 2515 (84%)
Out[20]:
2515
In [21]:
train_list = ['train']*train_size
test_list = ['test']*test_size
split_list = train_list + test_list

# 新增 split 欄位以區分資料為訓練集或測試集
df['split'] = split_list
df.head()
Out[21]:
Price Date Open High Low Close Adj Close Volume split
Ticker GC=F GC=F GC=F GC=F GC=F GC=F
0 2013-01-02 1672.800049 1693.800049 1670.000000 1687.900024 1687.900024 35 train
1 2013-01-03 1686.099976 1686.800049 1662.000000 1673.699951 1673.699951 140 train
2 2013-01-04 1647.000000 1658.300049 1625.699951 1648.099976 1648.099976 199 train
3 2013-01-07 1656.500000 1659.900024 1643.800049 1645.500000 1645.500000 49 train
4 2013-01-08 1647.699951 1661.500000 1647.699951 1661.500000 1661.500000 17 train
In [22]:
df.tail()
Out[22]:
Price Date Open High Low Close Adj Close Volume split
Ticker GC=F GC=F GC=F GC=F GC=F GC=F
2992 2024-11-25 2689.399902 2689.399902 2616.800049 2616.800049 2616.800049 94 test
2993 2024-11-26 2625.600098 2625.600098 2620.300049 2620.300049 2620.300049 177858 test
2994 2024-11-27 2633.500000 2657.899902 2627.199951 2639.899902 2639.899902 61653 test
2995 2024-11-28 2636.399902 2648.600098 2620.699951 2639.699951 2639.699951 61653 test
2996 2024-11-29 2636.399902 2664.300049 2620.699951 2657.000000 2657.000000 3861 test
In [23]:
# 訓練集與測試集黃金價格統計圖
fig = px.line(x=df[('Date', '')], y=df[('Close', 'GC=F')], color=df[('split', '')])
fig.update_layout(xaxis_title="Date", 
                  yaxis_title="Close Price",
                  title={'text': "圖3. GC價格走勢圖(2013-2013年)區分訓練集與測試集", 
                         'x':0.5, 
                         'y':0.95, 
                         'xanchor':'center', 
                         'yanchor':'top'},
                  plot_bgcolor='rgba(255, 215, 0, 0.5)')
fig.show()
In [24]:
# 使用 MinMaxScaler 來縮放價格以避免密集計算(intensive computations)
# MinMaxScaler 預設將資料轉換為 [0, 1]
scaler = MinMaxScaler()

# reshape(-1,1) 表示將維度改為電腦自動排列列數(-1)與1行資料, 即 reshape(列, 行)
scaler.fit(df.Close.values.reshape(-1,1))

# 建立滑動視窗(sliding window)
# 滑動視窗表示使用先前的時間序列資料來預測下一期的時間序列資料.

# 滑動視窗範例
# 考慮連續讀取2週資料, 即{1,2週}, {2,3週}, {3,4週}...
# window width(視窗寬度) = 14, 其中 Stride(步伐) = 7.

# 本研究視窗寬度設為 60, X_train 和 X_test 將是包含 60 個時間戳的收盤價list.
window_size = 60

# 原始訓練集
train_data = df.Close[:-test_size]

# 原始訓練集-資料轉換至 [0, 1]
train_data = scaler.transform(train_data.values.reshape(-1,1))

# 建立空的串列 (list)
X_train = []
y_train = []

# 使用"滑動視窗"建立訓練集
for i in range(window_size, len(train_data)):
    X_train.append(train_data[i-60:i, 0])
    y_train.append(train_data[i, 0])

# 檢視指標為0資料
X_train[0]
Out[24]:
array([0.36663405, 0.35846229, 0.34373019, 0.34223397, 0.35144154,
       0.3475859 , 0.36053405, 0.35057833, 0.35570006, 0.36404442,
       0.36364155, 0.36807274, 0.36588591, 0.36945389, 0.36571331,
       0.35604533, 0.34850664, 0.34620475, 0.35098114, 0.36203027,
       0.3509236 , 0.3559878 , 0.3593831 , 0.35771422, 0.36076418,
       0.35656327, 0.35403117, 0.34378772, 0.34407546, 0.34148583,
       0.33601883, 0.32111413, 0.31812162, 0.30315931, 0.30350458,
       0.30016688, 0.30810837, 0.3247971 , 0.31328763, 0.30321685,
       0.29987914, 0.29999421, 0.30143289, 0.30143289, 0.30154803,
       0.30258384, 0.30327445, 0.31115841, 0.30931692, 0.31064047,
       0.31173388, 0.3186971 , 0.32255281, 0.32036598, 0.32399149,
       0.31961784, 0.3186971 , 0.31363297, 0.31961784, 0.3130575 ])
In [25]:
y_train[0]
Out[25]:
0.3160499318662604
In [26]:
# 原始測試集
test_data = df.Close[-test_size-60:]

# 原始測試集-資料轉換至 [0, 1]
test_data = scaler.transform(test_data.values.reshape(-1,1))

# 建立空的串列 (list)
X_test = []
y_test = []

# 使用"滑動視窗"建立測試集
for i in range(window_size, len(test_data)):
    X_test.append(test_data[i-60:i, 0])
    y_test.append(test_data[i, 0])

X_test[0]
Out[26]:
array([0.38033028, 0.373885  , 0.35477932, 0.36133966, 0.35650574,
       0.35633307, 0.34004714, 0.34885191, 0.34424813, 0.33187545,
       0.33377454, 0.34539907, 0.34407546, 0.34643495, 0.35288023,
       0.35098114, 0.33883866, 0.33670944, 0.34194623, 0.34234904,
       0.33176038, 0.35777175, 0.36007364, 0.38056048, 0.37940953,
       0.4025436 , 0.41157851, 0.41595209, 0.41606723, 0.41560682,
       0.40858607, 0.40346435, 0.39511998, 0.39563792, 0.39943603,
       0.40427002, 0.39667373, 0.40145019, 0.40006904, 0.43177761,
       0.42878517, 0.41238418, 0.4134776 , 0.42280024, 0.42464172,
       0.43005119, 0.41992287, 0.43914369, 0.43546065, 0.41802378,
       0.42538987, 0.42406625, 0.44029464, 0.44029464, 0.42366345,
       0.42878517, 0.43966163, 0.43569085, 0.44236633, 0.4424814 ])
In [27]:
y_test[0]
Out[27]:
0.45399086407968736
In [28]:
# 將巢狀串列(nested lists) 轉換為 Numpy Arrays, 以利 tensorflow 操作.
X_train = np.array(X_train) 
X_test  = np.array(X_test)  
y_train = np.array(y_train)
y_test  = np.array(y_test)
In [29]:
# 轉換前維度
X_train.shape # (2455, 60)
Out[29]:
(2455, 60)
In [30]:
y_train.shape # (2455,)
Out[30]:
(2455,)
In [31]:
X_test.shape  # (482, 60)
Out[31]:
(482, 60)
In [32]:
y_test.shape  # (482,)
Out[32]:
(482,)
In [33]:
# 維度轉換
X_train = np.reshape(X_train, (X_train.shape[0], X_train.shape[1], 1))
X_test  = np.reshape(X_test, (X_test.shape[0], X_test.shape[1], 1))
y_train = np.reshape(y_train, (-1,1))
y_test  = np.reshape(y_test, (-1,1))

# 轉換後維度
print('X_train Shape: ', X_train.shape) # (2455, 60, 1)
print('y_train Shape: ', y_train.shape) # (2455, 1)
print('X_test Shape:  ', X_test.shape)  # (482, 60, 1)
print('y_test Shape:  ', y_test.shape)  # (482, 1)
X_train Shape:  (2455, 60, 1)
y_train Shape:  (2455, 1)
X_test Shape:   (482, 60, 1)
y_test Shape:   (482, 1)

步驟 4:建立模型¶

In [34]:
# LSTM 定義
def define_model():
    input1 = Input(shape=(window_size,1))
    x = LSTM(units = 64, return_sequences=True)(input1)  
    x = Dropout(0.2)(x)
    x = LSTM(units = 64, return_sequences=True)(x)
    x = Dropout(0.2)(x)
    x = LSTM(units = 64)(x)
    x = Dropout(0.2)(x)
    x = Dense(32, activation='softmax')(x)
    dnn_output = Dense(1)(x)
    model = Model(inputs=input1, outputs=[dnn_output])
    model.compile(loss='mean_squared_error', optimizer='Nadam')  
    return model

# 建立LSTM模型
model = define_model()

# 模型摘要
model.summary()
Model: "functional"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓
┃ Layer (type)                         ┃ Output Shape                ┃         Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩
│ input_layer (InputLayer)             │ (None, 60, 1)               │               0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ lstm (LSTM)                          │ (None, 60, 64)              │          16,896 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dropout (Dropout)                    │ (None, 60, 64)              │               0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ lstm_1 (LSTM)                        │ (None, 60, 64)              │          33,024 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dropout_1 (Dropout)                  │ (None, 60, 64)              │               0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ lstm_2 (LSTM)                        │ (None, 64)                  │          33,024 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dropout_2 (Dropout)                  │ (None, 64)                  │               0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dense (Dense)                        │ (None, 32)                  │           2,080 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dense_1 (Dense)                      │ (None, 1)                   │              33 │
└──────────────────────────────────────┴─────────────────────────────┴─────────────────┘
 Total params: 85,057 (332.25 KB)
 Trainable params: 85,057 (332.25 KB)
 Non-trainable params: 0 (0.00 B)
In [35]:
# 模型訓練, 執行150世代, 須一些時間...
history = model.fit(X_train, 
                    y_train, 
                    epochs=150, 
                    batch_size=32, 
                    validation_split=0.1, 
                    verbose=1)
Epoch 1/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 16s 73ms/step - loss: 0.0135 - val_loss: 0.0020
Epoch 2/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 57ms/step - loss: 0.0011 - val_loss: 0.0018
Epoch 3/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 57ms/step - loss: 8.2243e-04 - val_loss: 0.0015
Epoch 4/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 56ms/step - loss: 6.7939e-04 - val_loss: 0.0020
Epoch 5/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 59ms/step - loss: 6.3193e-04 - val_loss: 0.0010
Epoch 6/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 5s 62ms/step - loss: 6.0963e-04 - val_loss: 8.3797e-04
Epoch 7/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 61ms/step - loss: 5.0481e-04 - val_loss: 0.0028
Epoch 8/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 59ms/step - loss: 5.2247e-04 - val_loss: 6.9251e-04
Epoch 9/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 59ms/step - loss: 4.8771e-04 - val_loss: 6.5426e-04
Epoch 10/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 60ms/step - loss: 4.3651e-04 - val_loss: 5.7864e-04
Epoch 11/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 59ms/step - loss: 4.5843e-04 - val_loss: 0.0022
Epoch 12/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 56ms/step - loss: 5.1377e-04 - val_loss: 5.7116e-04
Epoch 13/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 55ms/step - loss: 3.6027e-04 - val_loss: 5.8829e-04
Epoch 14/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 57ms/step - loss: 3.9247e-04 - val_loss: 0.0079
Epoch 15/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 60ms/step - loss: 7.3538e-04 - val_loss: 4.8902e-04
Epoch 16/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 54ms/step - loss: 3.8104e-04 - val_loss: 4.3934e-04
Epoch 17/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 56ms/step - loss: 3.0965e-04 - val_loss: 4.0908e-04
Epoch 18/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 56ms/step - loss: 3.5978e-04 - val_loss: 0.0017
Epoch 19/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 55ms/step - loss: 3.6622e-04 - val_loss: 4.4799e-04
Epoch 20/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 53ms/step - loss: 2.8564e-04 - val_loss: 5.5605e-04
Epoch 21/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 57ms/step - loss: 3.1798e-04 - val_loss: 4.4426e-04
Epoch 22/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 5s 55ms/step - loss: 3.0281e-04 - val_loss: 5.6176e-04
Epoch 23/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 58ms/step - loss: 2.6238e-04 - val_loss: 0.0058
Epoch 24/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 5s 55ms/step - loss: 4.9221e-04 - val_loss: 3.8660e-04
Epoch 25/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 59ms/step - loss: 2.5532e-04 - val_loss: 3.6969e-04
Epoch 26/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 5s 60ms/step - loss: 2.3821e-04 - val_loss: 3.9765e-04
Epoch 27/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 61ms/step - loss: 2.2643e-04 - val_loss: 0.0015
Epoch 28/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 58ms/step - loss: 2.8469e-04 - val_loss: 0.0209
Epoch 29/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 57ms/step - loss: 0.0015 - val_loss: 4.5263e-04
Epoch 30/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 55ms/step - loss: 2.7566e-04 - val_loss: 3.4855e-04
Epoch 31/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 55ms/step - loss: 2.5061e-04 - val_loss: 3.1124e-04
Epoch 32/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 55ms/step - loss: 2.3381e-04 - val_loss: 2.8162e-04
Epoch 33/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 56ms/step - loss: 2.3776e-04 - val_loss: 3.6345e-04
Epoch 34/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 56ms/step - loss: 1.9509e-04 - val_loss: 0.0019
Epoch 35/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 55ms/step - loss: 2.7917e-04 - val_loss: 3.1317e-04
Epoch 36/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 55ms/step - loss: 2.3084e-04 - val_loss: 2.7915e-04
Epoch 37/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 54ms/step - loss: 2.0655e-04 - val_loss: 0.0056
Epoch 38/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 54ms/step - loss: 4.6409e-04 - val_loss: 0.0013
Epoch 39/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 54ms/step - loss: 2.8957e-04 - val_loss: 3.0516e-04
Epoch 40/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 5s 55ms/step - loss: 2.0871e-04 - val_loss: 2.5150e-04
Epoch 41/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 57ms/step - loss: 2.1179e-04 - val_loss: 2.2896e-04
Epoch 42/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 58ms/step - loss: 2.0112e-04 - val_loss: 4.1794e-04
Epoch 43/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 56ms/step - loss: 2.1626e-04 - val_loss: 0.0027
Epoch 44/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 55ms/step - loss: 3.3055e-04 - val_loss: 7.2929e-04
Epoch 45/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 54ms/step - loss: 2.2482e-04 - val_loss: 4.2469e-04
Epoch 46/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 56ms/step - loss: 1.9307e-04 - val_loss: 2.5870e-04
Epoch 47/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 55ms/step - loss: 2.0700e-04 - val_loss: 2.8799e-04
Epoch 48/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 55ms/step - loss: 1.9328e-04 - val_loss: 2.2711e-04
Epoch 49/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 56ms/step - loss: 1.9957e-04 - val_loss: 3.1137e-04
Epoch 50/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 56ms/step - loss: 1.8402e-04 - val_loss: 2.3101e-04
Epoch 51/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 58ms/step - loss: 1.8249e-04 - val_loss: 0.0026
Epoch 52/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 58ms/step - loss: 2.3914e-04 - val_loss: 2.0625e-04
Epoch 53/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 59ms/step - loss: 1.7848e-04 - val_loss: 0.0181
Epoch 54/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 59ms/step - loss: 0.0012 - val_loss: 3.3777e-04
Epoch 55/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 60ms/step - loss: 2.3957e-04 - val_loss: 2.5069e-04
Epoch 56/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 57ms/step - loss: 1.9173e-04 - val_loss: 3.8854e-04
Epoch 57/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 59ms/step - loss: 2.1495e-04 - val_loss: 2.3455e-04
Epoch 58/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 56ms/step - loss: 1.7794e-04 - val_loss: 3.1975e-04
Epoch 59/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 57ms/step - loss: 1.7984e-04 - val_loss: 2.3835e-04
Epoch 60/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 56ms/step - loss: 1.8619e-04 - val_loss: 2.0049e-04
Epoch 61/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 57ms/step - loss: 1.7823e-04 - val_loss: 2.1113e-04
Epoch 62/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 55ms/step - loss: 1.7694e-04 - val_loss: 2.1338e-04
Epoch 63/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 57ms/step - loss: 1.3932e-04 - val_loss: 2.0681e-04
Epoch 64/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 56ms/step - loss: 1.6159e-04 - val_loss: 4.8196e-04
Epoch 65/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 54ms/step - loss: 1.9957e-04 - val_loss: 1.9958e-04
Epoch 66/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 57ms/step - loss: 1.4645e-04 - val_loss: 3.2801e-04
Epoch 67/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 53ms/step - loss: 2.0433e-04 - val_loss: 4.5516e-04
Epoch 68/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 53ms/step - loss: 1.7559e-04 - val_loss: 0.0029
Epoch 69/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 54ms/step - loss: 3.4577e-04 - val_loss: 2.0766e-04
Epoch 70/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 53ms/step - loss: 1.6237e-04 - val_loss: 1.9299e-04
Epoch 71/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 54ms/step - loss: 1.5945e-04 - val_loss: 1.7875e-04
Epoch 72/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 52ms/step - loss: 1.6372e-04 - val_loss: 2.0219e-04
Epoch 73/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 52ms/step - loss: 1.4172e-04 - val_loss: 7.3591e-04
Epoch 74/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 53ms/step - loss: 1.7783e-04 - val_loss: 5.8954e-04
Epoch 75/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 52ms/step - loss: 1.7945e-04 - val_loss: 2.1279e-04
Epoch 76/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 53ms/step - loss: 1.7307e-04 - val_loss: 1.8075e-04
Epoch 77/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 53ms/step - loss: 1.5993e-04 - val_loss: 1.8825e-04
Epoch 78/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 51ms/step - loss: 1.4542e-04 - val_loss: 9.1237e-04
Epoch 79/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 53ms/step - loss: 1.7726e-04 - val_loss: 6.3092e-04
Epoch 80/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 51ms/step - loss: 1.5891e-04 - val_loss: 2.0318e-04
Epoch 81/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 52ms/step - loss: 1.6473e-04 - val_loss: 2.3599e-04
Epoch 82/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 54ms/step - loss: 1.4441e-04 - val_loss: 2.4296e-04
Epoch 83/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 53ms/step - loss: 1.5360e-04 - val_loss: 3.0701e-04
Epoch 84/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 55ms/step - loss: 1.3269e-04 - val_loss: 2.6922e-04
Epoch 85/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 54ms/step - loss: 1.6853e-04 - val_loss: 2.8925e-04
Epoch 86/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 53ms/step - loss: 1.4836e-04 - val_loss: 2.4284e-04
Epoch 87/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 55ms/step - loss: 1.3646e-04 - val_loss: 3.2277e-04
Epoch 88/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 54ms/step - loss: 1.4615e-04 - val_loss: 0.0015
Epoch 89/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 54ms/step - loss: 1.8841e-04 - val_loss: 1.6791e-04
Epoch 90/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 56ms/step - loss: 1.3596e-04 - val_loss: 2.2799e-04
Epoch 91/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 57ms/step - loss: 1.6643e-04 - val_loss: 2.2634e-04
Epoch 92/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 55ms/step - loss: 1.4414e-04 - val_loss: 2.3572e-04
Epoch 93/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 56ms/step - loss: 1.4605e-04 - val_loss: 1.7744e-04
Epoch 94/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 52ms/step - loss: 1.4012e-04 - val_loss: 2.4749e-04
Epoch 95/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 54ms/step - loss: 1.2488e-04 - val_loss: 1.6517e-04
Epoch 96/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 54ms/step - loss: 1.4539e-04 - val_loss: 0.0025
Epoch 97/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 5s 55ms/step - loss: 2.3382e-04 - val_loss: 6.0928e-04
Epoch 98/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 55ms/step - loss: 1.5916e-04 - val_loss: 1.6887e-04
Epoch 99/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 55ms/step - loss: 1.4051e-04 - val_loss: 1.4905e-04
Epoch 100/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 54ms/step - loss: 1.2507e-04 - val_loss: 3.2270e-04
Epoch 101/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 55ms/step - loss: 1.5692e-04 - val_loss: 3.3297e-04
Epoch 102/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 54ms/step - loss: 1.4735e-04 - val_loss: 0.0013
Epoch 103/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 57ms/step - loss: 1.8148e-04 - val_loss: 4.1339e-04
Epoch 104/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 5s 54ms/step - loss: 1.5161e-04 - val_loss: 2.9922e-04
Epoch 105/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 55ms/step - loss: 1.4324e-04 - val_loss: 1.9280e-04
Epoch 106/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 55ms/step - loss: 1.3656e-04 - val_loss: 3.3976e-04
Epoch 107/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 56ms/step - loss: 1.3601e-04 - val_loss: 2.1909e-04
Epoch 108/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 54ms/step - loss: 1.3582e-04 - val_loss: 2.2427e-04
Epoch 109/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 54ms/step - loss: 1.1513e-04 - val_loss: 1.7200e-04
Epoch 110/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 56ms/step - loss: 1.2777e-04 - val_loss: 2.4892e-04
Epoch 111/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 55ms/step - loss: 1.3034e-04 - val_loss: 1.7855e-04
Epoch 112/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 53ms/step - loss: 1.4196e-04 - val_loss: 1.7495e-04
Epoch 113/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 55ms/step - loss: 1.3059e-04 - val_loss: 3.1878e-04
Epoch 114/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 5s 56ms/step - loss: 1.2429e-04 - val_loss: 2.6571e-04
Epoch 115/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 54ms/step - loss: 1.1911e-04 - val_loss: 1.7089e-04
Epoch 116/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 56ms/step - loss: 1.4085e-04 - val_loss: 2.1836e-04
Epoch 117/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 54ms/step - loss: 1.3066e-04 - val_loss: 3.0685e-04
Epoch 118/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 54ms/step - loss: 1.1508e-04 - val_loss: 2.8049e-04
Epoch 119/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 54ms/step - loss: 1.2290e-04 - val_loss: 2.3732e-04
Epoch 120/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 54ms/step - loss: 1.2022e-04 - val_loss: 1.8765e-04
Epoch 121/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 55ms/step - loss: 1.3913e-04 - val_loss: 1.5724e-04
Epoch 122/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 56ms/step - loss: 1.3140e-04 - val_loss: 4.3183e-04
Epoch 123/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 55ms/step - loss: 1.5729e-04 - val_loss: 2.2192e-04
Epoch 124/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 55ms/step - loss: 1.2983e-04 - val_loss: 4.7211e-04
Epoch 125/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 57ms/step - loss: 1.4638e-04 - val_loss: 1.4660e-04
Epoch 126/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 57ms/step - loss: 1.3205e-04 - val_loss: 2.8801e-04
Epoch 127/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 58ms/step - loss: 1.5522e-04 - val_loss: 1.7456e-04
Epoch 128/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 5s 57ms/step - loss: 1.1990e-04 - val_loss: 0.0056
Epoch 129/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 58ms/step - loss: 4.7419e-04 - val_loss: 0.0016
Epoch 130/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 55ms/step - loss: 2.1477e-04 - val_loss: 1.5832e-04
Epoch 131/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 56ms/step - loss: 1.4953e-04 - val_loss: 1.9310e-04
Epoch 132/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 58ms/step - loss: 1.3170e-04 - val_loss: 1.9438e-04
Epoch 133/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 59ms/step - loss: 1.4689e-04 - val_loss: 1.4154e-04
Epoch 134/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 55ms/step - loss: 1.3876e-04 - val_loss: 1.3877e-04
Epoch 135/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 54ms/step - loss: 1.1663e-04 - val_loss: 1.5793e-04
Epoch 136/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 55ms/step - loss: 1.3193e-04 - val_loss: 1.3858e-04
Epoch 137/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 56ms/step - loss: 1.1990e-04 - val_loss: 1.7469e-04
Epoch 138/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 56ms/step - loss: 1.2281e-04 - val_loss: 0.0019
Epoch 139/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 56ms/step - loss: 1.9644e-04 - val_loss: 1.4447e-04
Epoch 140/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 57ms/step - loss: 1.2311e-04 - val_loss: 3.5130e-04
Epoch 141/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 5s 56ms/step - loss: 1.2565e-04 - val_loss: 2.9709e-04
Epoch 142/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 56ms/step - loss: 1.2152e-04 - val_loss: 1.9254e-04
Epoch 143/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 56ms/step - loss: 1.4097e-04 - val_loss: 1.5970e-04
Epoch 144/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 5s 55ms/step - loss: 1.2158e-04 - val_loss: 1.6341e-04
Epoch 145/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 53ms/step - loss: 1.2063e-04 - val_loss: 4.8937e-04
Epoch 146/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 54ms/step - loss: 1.2041e-04 - val_loss: 6.8037e-04
Epoch 147/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 54ms/step - loss: 1.2991e-04 - val_loss: 2.2237e-04
Epoch 148/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 55ms/step - loss: 1.1744e-04 - val_loss: 1.6002e-04
Epoch 149/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 54ms/step - loss: 1.1543e-04 - val_loss: 4.4911e-04
Epoch 150/150
70/70 ━━━━━━━━━━━━━━━━━━━━ 4s 54ms/step - loss: 1.4177e-04 - val_loss: 3.4288e-04

步驟 5:建立評估與測試¶

In [36]:
# 模型評估
result = model.evaluate(X_test, y_test)

# 模型預測
y_pred = model.predict(X_test) 

# MAPE (Mean Absolute Percentage Error) 平均絕對百分比誤差率
MAPE = mean_absolute_percentage_error(y_test, y_pred)

# 正確率
Accuracy = 1 - MAPE

# 顯示評估結果
print("Test Loss:", result)
print("Test MAPE:", MAPE)         # 錯誤率 13.37%
print("Test Accuracy:", Accuracy) # 正確率 86.63%
16/16 ━━━━━━━━━━━━━━━━━━━━ 0s 19ms/step - loss: 0.0084
16/16 ━━━━━━━━━━━━━━━━━━━━ 1s 60ms/step
Test Loss: 0.029436124488711357
Test MAPE: 0.15208566629513215
Test Accuracy: 0.8479143337048678
In [37]:
# 測試集評估結果的視覺化

# 轉換原刻度單位
y_test_true = scaler.inverse_transform(y_test)
y_test_pred = scaler.inverse_transform(y_pred)

# 使用 matplotlib 繪圖

# 設定繪圖區域
plt.figure(figsize=(15, 6), dpi=150)

# 設定繪圖區域顏色
plt.rcParams['axes.facecolor'] = '#FFD700'

# train data
plt.plot(df['Date'].iloc[:-test_size], scaler.inverse_transform(train_data), color='black', lw=2)

# actual test data
plt.plot(df['Date'].iloc[-test_size:], y_test_true, color='blue', lw=2)

# predicted test data
plt.plot(df['Date'].iloc[-test_size:], y_test_pred, color='red', lw=2)

# 主標題
plt.title('圖4. Model Performance on Gold Price Prediction', fontsize=15)

# X軸標題
plt.xlabel('Date', fontsize=12)

# Y軸標題
plt.ylabel('Price', fontsize=12)

# 圖例
plt.legend(['Training Data', 'Actual Test Data', 'Predicted Test Data'], loc='upper left', prop={'size': 15})

# 格線顏色
plt.grid(color='white')
plt.show()

# 結論: 
# 本例使用 LSTM 模型進行黃金價格預測, 執行結果佳.
# 🏆 Loss:     13%
# 🏆 Accuracy: 87%
No description has been provided for this image

步驟 6:佈署應用¶

  1. 儲存模型
  2. 使用 Streamlit 建立互動式視覺化, 參考: https://youtu.be/-_zghs2qrIg?si=epkk5_CkidFhXDoU
  3. 改善線上執行效能
  4. 將此模型套用至工作資料集

3. 台灣股市,ETF下載¶

In [38]:
# 台灣ETF列表
# https://www.stockq.org/etf/

import yfinance as yf
import plotly.graph_objects as go

# 設定 plotly 繪圖結果顯示於瀏覽器
# 使用 Colab, Jupyter-notebook 不用設定此選項
# import plotly.io as pio
# pio.renderers.default='browser'
In [39]:
# mplfinance: matplotlib utilities for the visualization, and visual analysis, of financial data
# https://pypi.org/project/mplfinance/

# 上市: 台積電 '2330.TW'
# 上櫃: 崇友 '4506.TWO'
# ETF: '0050.TW'
# ETF: '0056.TW'
# ETF: '00878.TW'

# 擷取 元大台灣50ETF, 開始/結束資料
df = yf.download('0050.TW', start = '2008-10-03', end = '2023-12-31')
df
[*********************100%***********************]  1 of 1 completed
Out[39]:
Price Adj Close Close High Low Open Volume
Ticker 0050.TW 0050.TW 0050.TW 0050.TW 0050.TW 0050.TW
Date
2008-10-03 31.524755 44.610001 44.610001 44.610001 44.610001 0
2008-10-06 30.090202 42.580002 42.580002 42.580002 42.580002 0
2008-10-07 30.281006 42.849998 42.849998 42.849998 42.849998 0
2008-10-08 28.493116 40.320000 40.320000 40.320000 40.320000 0
2008-10-09 28.168049 39.860001 39.860001 39.860001 39.860001 0
... ... ... ... ... ... ...
2023-12-25 129.794617 133.500000 133.800003 132.949997 133.000000 8080412
2023-12-26 130.523788 134.250000 134.399994 133.500000 133.500000 16816964
2023-12-27 131.982162 135.750000 135.899994 134.350006 134.399994 29950035
2023-12-28 131.982162 135.750000 136.000000 135.449997 135.750000 14848296
2023-12-29 131.690475 135.449997 136.000000 135.350006 135.699997 13111608

3746 rows × 6 columns

In [40]:
# 擷取 元大台灣50 ETF, 最近期間/頻率資料, 近5天(包括系統日期), 每隔1分鐘資料
df = yf.download('0050.TW', period = '5d', interval = '1m')
df
[*********************100%***********************]  1 of 1 completed
Out[40]:
Price Adj Close Close High Low Open Volume
Ticker 0050.TW 0050.TW 0050.TW 0050.TW 0050.TW 0050.TW
Datetime
2024-12-02 01:00:00+00:00 189.199997 189.199997 189.199997 188.500000 188.500000 0
2024-12-02 01:01:00+00:00 189.199997 189.199997 189.250000 189.100006 189.250000 76000
2024-12-02 01:02:00+00:00 189.199997 189.199997 189.250000 189.050003 189.100006 101000
2024-12-02 01:03:00+00:00 189.199997 189.199997 189.250000 189.100006 189.149994 83000
2024-12-02 01:04:00+00:00 189.300003 189.300003 189.449997 189.199997 189.199997 92000
... ... ... ... ... ... ...
2024-12-06 05:20:00+00:00 195.449997 195.449997 195.649994 195.449997 195.649994 11541
2024-12-06 05:21:00+00:00 195.649994 195.649994 195.649994 195.550003 195.649994 38066
2024-12-06 05:22:00+00:00 195.550003 195.550003 195.649994 195.550003 195.550003 68743
2024-12-06 05:23:00+00:00 195.699997 195.699997 195.750000 195.550003 195.600006 49539
2024-12-06 05:24:00+00:00 195.699997 195.699997 195.750000 195.699997 195.699997 28060

1318 rows × 6 columns

In [41]:
# 建立財金資料擷取函數, 名稱為 getData
def getData(symbol, startDate, endDate):
    
    # 擷取財金資料
    data = yf.download(tickers = symbol, start = startDate, end = endDate)
    
    # 將 index 轉換為 Date 資料行
    data['Date'] = data.index

    # 將 Date 移至第1欄位置
    data = data[['Date', 'Open', 'High', 'Low', 'Close', 'Adj Close', 'Volume']]

    # 刪除 index 值
    data = data.reset_index(drop=True)
    
    # 回傳財金資料
    return data

# 使用自訂函數 getData 擷取資料
df = getData('0050.TW', '2024-01-01', '2024-11-30')
df
[*********************100%***********************]  1 of 1 completed
Out[41]:
Price Date Open High Low Close Adj Close Volume
Ticker 0050.TW 0050.TW 0050.TW 0050.TW 0050.TW 0050.TW
0 2024-01-02 135.600006 135.949997 134.649994 134.899994 131.155746 5922076
1 2024-01-03 133.699997 133.899994 132.300003 132.550003 128.870987 13547475
2 2024-01-04 132.550003 132.750000 132.300003 132.500000 128.822372 4567593
3 2024-01-05 132.550003 132.949997 132.100006 132.149994 128.482071 3999671
4 2024-01-08 133.000000 133.600006 132.750000 132.750000 129.065430 9394720
... ... ... ... ... ... ... ...
215 2024-11-25 194.699997 194.750000 192.250000 192.350006 192.350006 7826663
216 2024-11-26 190.100006 190.300003 188.800003 189.850006 189.850006 11390822
217 2024-11-27 189.149994 189.449997 187.000000 187.100006 187.100006 14072686
218 2024-11-28 187.000000 187.100006 185.449997 186.800003 186.800003 13109114
219 2024-11-29 184.949997 188.000000 184.500000 187.250000 187.250000 8128220

220 rows × 7 columns

In [42]:
df.columns
Out[42]:
MultiIndex([(     'Date',        ''),
            (     'Open', '0050.TW'),
            (     'High', '0050.TW'),
            (      'Low', '0050.TW'),
            (    'Close', '0050.TW'),
            ('Adj Close', '0050.TW'),
            (   'Volume', '0050.TW')],
           names=['Price', 'Ticker'])
In [43]:
# 使用 plotly 模組繪製 K 線圖, 預設值綠色表示上漲, 紅色表示下趺.
# K線圖採用歐美標準,  綠色表示上漲(收盤價高於開盤價), 紅色表示下趺(收盤價低於開盤價)
# 台灣等亞洲一般使用: 紅色表示上漲(收盤價高於開盤價), 綠色表示下趺(收盤價低於開盤價)

# K線(英語:Candlestick chart)又稱陰陽燭、蠟燭線.
# https://zh.wikipedia.org/wiki/K线

fig = go.Figure(data = [go.Candlestick(x=df[('Date', '')],
                open = df[('Open', '0050.TW')],
                high = df[('High', '0050.TW')],
                low = df[('Low', '0050.TW')],
                close = df[('Close', '0050.TW')],
                increasing_line_color = '#FF4136', # 設定上漲為紅色
                decreasing_line_color = '#3D9970'  # 
                )])
# fig.update_layout(xaxis_rangeslider_visible=False)
fig.update_layout(xaxis_rangeslider_visible=False,  # 取消成交量繪圖
                  xaxis_title="日期",               # 設定X軸標題
                  yaxis_title="收盤價",             # 設定Y軸標題
                  title={'text':'元大台灣50ETF收盤價走勢圖', 'x':0.5, 'y':0.95,  'xanchor':'center', 'yanchor':'top'})
fig.show()

進出場流程¶

  1. 選擇交易標的
  2. 進場判斷
  3. 出場判斷

交易策略¶

  1. 選擇交易標的: 考慮 0050
  2. 進場判斷: 當天K線是紅K,並且下引線是實體紅K的2倍.
  3. 出場判斷: 最少持有時間為3日, 3日過後只要當日紅K則出場.

參考資料¶

  1. Python:股票×ETF量化交易實戰105個活用技巧, 劉承彥著, 博碩文化.
  2. Python 與 Streamlit 模組 - 登山路線視覺化分析平台, https://youtu.be/-_zghs2qrIg?si=wlwT3Oj-F949-Gaw
  3. Gold Price Prediction | LSTM | 96% Accuracy, https://www.kaggle.com/code/farzadnekouei/gold-price-prediction-lstm-96-accuracy
In [ ]: